package puzzle.slilin2.data;

import java.util.Arrays;

import puzzle.common.data.AbstractBoard;
import puzzle.common.point.Direction;
import puzzle.common.point.Pnt;
import tomoto.assertion.ParamUtil;
import tomoto.util.ArrayUtil;
import tomoto.util.RectArray;

/**
 * 盤面。
 * 
 * @author tomoto
 */
public class SlilinBoard extends AbstractBoard<SlilinBoard> {
	private int m_logicalWidth;
	private int m_logicalHeight;

	// private FastBitSet m_dirtyBitmap;
	private boolean[] m_dirtyFlags;

	/**
	 * セル状態。下記のような配列になっている。一番外側の周囲は番人。
	 * 
	 * N|N|N<br>
	 * -.-.-<br>
	 * N|N|N<br>
	 * -.-.-<br>
	 * N|N|N
	 */
	private RectArray<CellState> m_states;

	/**
	 * 盤面の構築。
	 * 
	 * @param logicalWidth 盤面の論理的な幅。
	 * @param logicalHeight 盤面の論理的な高さ。
	 */
	public SlilinBoard(int logicalWidth, int logicalHeight) {
		super(logicalWidth * 2 + 3, logicalHeight * 2 + 3);
		m_logicalWidth = logicalWidth;
		m_logicalHeight = logicalHeight;

		m_states = new RectArray<CellState>(width, height, CellState.class);
		// m_dirtyBitmap = new FastBitSet(width * height);
		m_dirtyFlags = new boolean[width * height];
		initCellStates();
		dirtyAll();
	}

	/**
	 * コピーコンストラクタ。
	 */
	public SlilinBoard(SlilinBoard b) {
		super(b);
		m_logicalWidth = b.m_logicalWidth;
		m_logicalHeight = b.m_logicalHeight;

		m_states = new RectArray<CellState>(width, height, CellState.class);
		// m_dirtyBitmap = new FastBitSet(b.m_dirtyBitmap);
		m_dirtyFlags = ArrayUtil.dup(b.m_dirtyFlags);
		copyCellStates(b);
	}

	private void initCellStates() {
		// 盤面に初期状態のオブジェクトを敷き詰める
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				Pnt p = new Pnt(x, y);
				switch (SlilinPntHelper.getCellType(p)) {
				case NUMBER:
					m_states.set(x, y, new NumberCellState());
					break;
				case DOT:
					m_states.set(x, y, new DotCellState());
					break;
				case HLINE:
				case VLINE:
					m_states.set(x, y, new LineCellState());
					break;
				}
			}
		}

		// 上下の端に番人として線禁止を書き込む
		for (int x = 1; x < width; x += 2) {
			getLineCellState(x, 0).setForbidden(true);
			getLineCellState(x, height - 1).setForbidden(true);
		}

		// 左右の端も同様
		for (int y = 1; y < height; y += 2) {
			getLineCellState(0, y).setForbidden(true);
			getLineCellState(width - 1, y).setForbidden(true);
		}
	}

	private void copyCellStates(SlilinBoard b) {
		for (int i = 0; i < width * height; i++) {
			m_states.elements[i] = b.m_states.elements[i].dup();
		}
	}

	public int getLogicalWidth() {
		return m_logicalWidth;
	}

	public int getLogicalHeight() {
		return m_logicalHeight;
	}

	public int getNumber(Pnt p) {
		// ParamUtil.check(PntHelper.getCellType(p) == CellType.NUMBER);
		// return ((NumberCellState) m_states.getElement(p.x, p.y)).getNumber();
		// return ((NumberCellState) m_states.elements[p.x + p.y * m_states.width]).getNumber();
		return getNumber(p.x, p.y);
	}

	public int getNumber(int x, int y) {
		// return ((NumberCellState) m_states.getElement(x, y)).getNumber();
		return ((NumberCellState) m_states.elements[x + y * m_states.width]).getNumber();
	}

	public void setNumber(Pnt p, int number) {
		ParamUtil.check(SlilinPntHelper.getCellType(p) == CellType.NUMBER);
		((NumberCellState) m_states.get(p.x, p.y)).setNumber(number);
	}

	public LineCellState getLineCellState(Pnt p) {
		// ParamUtil.check(PntHelper.isCellTypeLine(p));
		// return (LineCellState) m_states.getElement(p.x, p.y);
		// return (LineCellState) m_states.elements[p.x + p.y * m_states.width];
		return getLineCellState(p.x, p.y);
	}

	public LineCellState getLineCellState(int x, int y) {
		// return (LineCellState) m_states.getElement(x, y);
		return (LineCellState) m_states.elements[x + y * m_states.width];
	}

	public DotCellState getDotCellState(Pnt p) {
		// ParamUtil.check(PntHelper.getCellType(p) == CellType.DOT);
		// return (DotCellState) m_states.getElement(p.x, p.y);
		// return (DotCellState) m_states.elements[p.x + p.y * m_states.width];
		return getDotCellState(p.x, p.y);
	}

	public DotCellState getDotCellState(int x, int y) {
		// return (DotCellState) m_states.getElement(x, y);
		return (DotCellState) m_states.elements[x + y * m_states.width];
	}

	public boolean isNumberFixed(Pnt p) {
		ParamUtil.check(SlilinPntHelper.getCellType(p) == CellType.NUMBER);

		return getLineCellState(p.add(Direction.N.v)).isFixed()
				&& getLineCellState(p.add(Direction.E.v)).isFixed()
				&& getLineCellState(p.add(Direction.S.v)).isFixed()
				&& getLineCellState(p.add(Direction.W.v)).isFixed();
	}

	public boolean isComplete() {
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				if (SlilinPntHelper.isCellTypeLine(x, y)) {
					if (!getLineCellState(x, y).isFixed()) {
						return false;
					}
				}
			}

		}
		return true;
	}

	public SlilinBoard dup() {
		return new SlilinBoard(this);
	}

	public boolean isInside(Pnt p) {
		return (p.x >= 0 && p.x < width && p.y >= 0 && p.y < height);
	}

	public boolean isDirty(int x, int y) {
		// return m_dirtyBitmap.get(x + y * width);
		return m_dirtyFlags[x + y * width];
	}
	
	public boolean[] getDirtyFlags() {
		return m_dirtyFlags;
	}

	public void dirtyAll() {
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				m_states.get(x, y).setDirty(true);
			}
		}
		// m_dirtyBitmap.setAll(true);
		Arrays.fill(m_dirtyFlags, true);
	}

	@Override
	protected void copyFromImpl(SlilinBoard other) {
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				m_states.get(x, y).copyFrom(other.m_states.get(x, y));
			}
		}
	}

	@Override
	public void snapshotDirtyFlags() {
		Arrays.fill(m_dirtyFlags, false);
		// m_dirtyBitmap.clear();
		final int maxIndex = width * height;
		for (int i = 0; i < maxIndex; i++) {
			CellState s = m_states.elements[i];
			if (s.isDirty()) {
				// m_dirtyBitmap.set(i, true);
				m_dirtyFlags[i] = true;
				s.setDirty(false);
			}
		}

//		FastBitSet mask;
//		mask = new FastBitSet(m_dirtyBitmap);
//		mask.shift(-1);
//		m_dirtyBitmap.or(mask);
//		mask.shift(-1);
//		m_dirtyBitmap.or(mask);
//
//		mask.copyFrom(m_dirtyBitmap);
//		mask.shift(+2);
//		m_dirtyBitmap.or(mask);
//
//		mask.copyFrom(m_dirtyBitmap);
//		mask.shift(-width);
//		m_dirtyBitmap.or(mask);
//		mask.shift(-width);
//		m_dirtyBitmap.or(mask);
//
//		mask.copyFrom(m_dirtyBitmap);
//		mask.shift(+width * 2);
//		m_dirtyBitmap.or(mask);
//		
//		for (int i = 0; i < width * height; i++) {
//			m_dirtyBooleans[i] = m_dirtyBitmap.get(i);
//		}

		boolean[] tmp;
		tmp = ArrayUtil.dup(m_dirtyFlags);
		for (int i = width; i < maxIndex - width; i++) {
			m_dirtyFlags[i] |= tmp[i - 2] | tmp[i - 1] | tmp[i + 1] | tmp[i + 2];
		}
		tmp = ArrayUtil.dup(m_dirtyFlags);
		for (int i = width; i < width * 2; i++) {
			m_dirtyFlags[i] |= tmp[i - width] | tmp[i + width] | tmp[i + width * 2];
		}
		for (int i = width * 2; i < maxIndex - width * 2; i++) {
			m_dirtyFlags[i] |= tmp[i - width * 2] | tmp[i - width] | tmp[i + width] | tmp[i + width * 2];
		}
		for (int i = maxIndex - width * 2; i < maxIndex - width; i++) {
			m_dirtyFlags[i] |= tmp[i - width] | tmp[i + width];
		}

	}
}
